package com.clarence.dada.core.auth.service.impl;


import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.clarence.dada.core.auth.config.SecurityConfigExpander;
import com.clarence.dada.core.auth.constants.LoginCacheConstants;
import com.clarence.dada.core.auth.context.LoginContext;
import com.clarence.dada.core.auth.entity.LoginUser;
import com.clarence.dada.core.auth.entity.dto.LoginDto;
import com.clarence.dada.core.auth.entity.dto.UserValidateDTO;
import com.clarence.dada.core.auth.jwt.DefaultJwtPayload;
import com.clarence.dada.core.auth.expander.AuthConfigExpander;
import com.clarence.dada.core.auth.jwt.JwtTokenUtil;
import com.clarence.dada.core.auth.service.AuthService;
import com.clarence.dada.core.auth.service.PasswordStoredEncryptApi;
import com.clarence.dada.core.auth.service.PasswordTransferEncryptApi;
import com.clarence.dada.core.auth.session.SessionManagerApi;
import com.clarence.dada.core.redis.RedisOperatorApi;
import com.clarence.dada.core.enums.ErrorEnum;
import com.clarence.dada.core.exception.AuthException;
import com.clarence.dada.core.exception.BusException;
import com.clarence.dada.modular.system.user.entity.req.LoginReq;
import com.clarence.dada.modular.system.user.enums.UserStatusEnum;
import com.clarence.dada.modular.system.user.service.DragCaptchaService;
import com.clarence.dada.modular.system.user.service.SysUserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import java.util.Set;

/**
 * 认证服务的实现
 *
 * @author GD
 * @since 2020/10/20 10:25
 */
@Service
public class AuthServiceImpl implements AuthService {

    @Resource(name = "loginErrorCountCacheApi")
    private RedisOperatorApi<Integer> loginErrorCountCacheApi;

    @Resource
    private SysUserService sysUserService;

    @Resource
    private SessionManagerApi sessionManagerApi;

    @Resource
    private DragCaptchaService dragCaptchaService;

    @Resource(name = "loginUserCache")
    private RedisOperatorApi<LoginUser> loginUserCache;

    @Resource(name = "allPlaceLoginTokenCache")
    private RedisOperatorApi<Set<String>> allPlaceLoginTokenCache;

    @Resource
    private PasswordStoredEncryptApi passwordStoredEncryptApi;

    @Resource
    private PasswordTransferEncryptApi passwordTransferEncryptApi;

    @Override
    public LoginDto login(LoginReq req) {
        // 1.参数为空校验
        if (req == null || StrUtil.hasBlank(req.getAccount(), req.getPassword())) {
            throw new AuthException(ErrorEnum.PARAM_EMPTY);
        }

        // 1.2 判断账号是否密码重试次数过多被冻结
        Integer loginErrorCount = loginErrorCountCacheApi.get(req.getAccount());
        if (loginErrorCount != null && loginErrorCount >= LoginCacheConstants.MAX_ERROR_LOGIN_COUNT) {
            throw new AuthException(ErrorEnum.LOGIN_LOCKED);
        }


        // 2. 验证拖拽验证码
        if (SecurityConfigExpander.getDragCaptchaOpen()) {
            String verKey = req.getVerKey();
            String verLocationValueX = req.getVerCode();

            if (StrUtil.isEmpty(verKey) || StrUtil.isEmpty(verLocationValueX)) {
                throw new BusException(ErrorEnum.CAPTCHA_EMPTY);
            }
            if (!dragCaptchaService.validateCaptcha(verKey, Convert.toInt(verLocationValueX))) {
                throw new BusException(ErrorEnum.DRAG_CAPTCHA_ERROR);
            }
        }

        // 3. 解密密码的密文，需要sys_config相关配置打开
        if (req.getPassword() != null && AuthConfigExpander.getPasswordRsaValidateFlag()) {
            String decryptPassword = passwordTransferEncryptApi.decrypt(req.getPassword());
            req.setPassword(decryptPassword);
        }

        // 5. 获取用户密码的加密值和用户的状态
        UserValidateDTO userValidateInfo = sysUserService.getUserLoginValidateDTO(req.getAccount());


        // 6. 校验用户密码是否正确
        validateUserPassword(loginErrorCount, req, userValidateInfo);

        // 7. 校验用户是否异常（不是正常状态）
        if (!UserStatusEnum.ENABLE.getCode().equals(userValidateInfo.getUserStatus())) {
            throw new BusException(ErrorEnum.USER_STATUS_ERROR);
        }


        // 8. 生成用户的token
        DefaultJwtPayload defaultJwtPayload = new DefaultJwtPayload(userValidateInfo.getUserId(), userValidateInfo.getAccount(), req.getRememberMe());
        String userLoginToken = JwtTokenUtil.generateTokenDefaultPayload(defaultJwtPayload);

        // 9. 创建loginUser对象
        LoginUser loginUser = new LoginUser(userValidateInfo.getUserId(), req.getAccount(), userLoginToken);

        synchronized (loginUser.getAccount().intern()) {

            // 10. 缓存用户信息，创建会话
            sessionManagerApi.createSession(userLoginToken, loginUser, req.getCreateCookie());

            // 11. 如果开启了单账号单端在线，则踢掉已经上线的该用户
            if (AuthConfigExpander.getSingleAccountLoginFlag()) {
                sessionManagerApi.removeSessionExcludeToken(userLoginToken);
            }
        }

        // 12. 更新用户登录时间和ip

        // 13.登录成功日志
//        loginLogServiceApi.loginSuccess(loginUser.getUserId());


        // 13.1 登录成功，清空用户的错误登录次数
        loginErrorCountCacheApi.remove(req.getAccount());

        // 14. 组装返回结果
        return new LoginDto(loginUser.getUserId(), userLoginToken);
    }

    private void validateUserPassword(Integer loginErrorCount, LoginReq req, UserValidateDTO userValidateInfo) {
        Boolean checkResult = passwordStoredEncryptApi.checkPassword(req.getPassword(), userValidateInfo.getUserPasswordHexed());

        // 校验用户表密码是否正确，如果正确则直接返回
        if (checkResult) {
            return;
        }

        // 临时秘钥校验完成，返回前端用户密码错误
        if (loginErrorCount == null) {
            loginErrorCount = 0;
        }
        // 记录error次数
        loginErrorCountCacheApi.put(req.getAccount(), loginErrorCount + 1);

        throw new AuthException(ErrorEnum.USERNAME_PASSWORD_ERROR);
    }


    @Override
    public void logout() {
        String token = LoginContext.me().getToken();
        // 记录退出日志

        // 清除token缓存的用户信息
        removeSession(token);
    }

    public void removeSession(String token) {

        LoginUser loginUser = loginUserCache.get(token);

        // 删除本token用户信息的缓存
        loginUserCache.remove(token);

        // 删除多端登录信息
        if (loginUser != null) {
            Long userId = loginUser.getUserId();
            Set<String> userTokens = allPlaceLoginTokenCache.get(userId.toString());
            if (userTokens != null) {

                // 清除对应的token的信息
                userTokens.remove(token);
                allPlaceLoginTokenCache.put(userId.toString(), userTokens);

                // 如果删除后size为0，则把整个key删掉
                if (userTokens.size() == 0) {
                    allPlaceLoginTokenCache.remove(userId.toString());
                }
            }
        }
    }

}
